/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package se.kth.lwr.jeat.utils;

import javax.measure.quantity.Duration;
import javax.measure.quantity.Quantity;
import javax.measure.unit.Unit;
import org.jscience.physics.amount.Amount;
import se.kth.lwr.jeat.core.*;

/**
 * Supporter class for easy construction of different types of productions. 
 * It is centered around the {@link MultiplierActivity} class which it uses 
 * to build Activities. Basically it relies on three important elements:
 * 
 * <ul>
 * <li>
 * An underlying {@link InventoryDataSource} (the product data source - also
 * named multiplicand in the MultiplierActivity)
 * </li>
 * 
 * <li>
 * An {@link AmountConverter} that helps to convert AmountDataSources to the 
 * dimension that the underlying data source supports.
 * </li>
 * <li>
 * It also extensively use the {@link AmountFunction} that provides
 * aggregation of multiple AmountDataSources in simple mathematical functions
 * </li>
 * </ul>
 * <p/>
 * 
 * @see AmountFunction
 * @see AmountDataSource
 * @see InventoryDataSource
 * @see MultiplierActivity
 * 
 *
 * @author davidev
 *
 * 
 */
public class ProductionSupport {
    
    //AmounManager responsible for Amount argument treatment
    private AmountConverter amountConverter;
    //DataSource
    private DataSource<Inventoriable> dataSource;
    //Name of the product/result
    private String productName;
    
    /**
     * Creates a productionSupport with a DefaultAmountManager that accepts
     * only units compatible with the unit parameter. For example, if the 
     * Inventory holds values of MJ/kg the inventory-compatible units are of
     * type <code>Unit<Mass></code> (e.g. g, kg, pound etc).
     * 
     * Här kan man tycka att det skulle varit snyggare att ha en Quantity som
     * parameter istället för Unit. Jag har dock fortfarande inte hittat nåt sätt 
     * att undersöka om en Unit tillhör en specifik Quantity. Däremot går det enkelt att 
     * se om en Unit är kompatibel medd en annan Unit. Detta är anledningen till
     * att argumentet är en Unit.
     * 
     * @param productName name of the product
     * @param datasource underlying Inventory for the product
     * @param unit the Inventory-compatible Unit
     */

    public ProductionSupport(String productName, DataSource<Inventoriable> datasource, Unit<? extends Quantity> unit) {
        this.productName = productName;
        this.dataSource = datasource;
        amountConverter = new DefaultAmountConverter(unit);
    }
    
    /**creates a ProductionSupport with a custom AmountConverter
     * 
     * @param productName name of product
     * @param dataSource the product InventoryDataSource
     * @param amountConverter a custom AmountConverter
     */

    public ProductionSupport(String productName, InventoryDataSource dataSource, AmountConverter amountConverter) {
        this.amountConverter = amountConverter;
        this.dataSource = dataSource;
        this.productName = productName;
    }
    
    /**
     * 
     * @return the current AmountConverter
     */
    public AmountConverter getAmountConverter() {
        return amountConverter;
    }
    
    /**Set the current AmountConverter
     * 
     * @param amountConverter the new AmountConverter
     */

    public void setAmountConverter(AmountConverter amountConverter) {
        this.amountConverter = amountConverter;
    }
    
    /**
     * get the name of the product
     * @return 
     */
    public String getProductName() {
        return productName;
    }
    
    /**
     * Internally used to wrap volume arguments in AmountDataSource objects
     * 
     * @param volume the Amount to wrap
     * @return an AmountDataSource
     */

    protected AmountDataSource wrapVolumeArgument(Amount volume) {
        String name = "amount of " + getProductName();
        String description = "the amount of " + getProductName() + " used in this contex";
        return new AmountDataSource(name, description, volume);

    }
    
     /**
     * Internally used to wrap lifetime arguments in AmountDataSource objects.
     *
     * @param duration the Amount to wrap
     * @return an AmountDataSource
     * 
     * Här är ett typiskt exempel där man enligt regelbooken borde kräva en
     * Amount<duration> jag har dock valt att låta bli detta eftersom det 
     * kräver mer arbete för API-användaren
     */

    protected AmountDataSource wrapLifetimeArgument(Amount duration) {
        return new AmountDataSource<>(
                getProductName() + " lifetime",
                "the estimated lifetime of " + getProductName() + " in this contex",
                duration);
    }

    /**
     * The most primitive build method with no use of the AmountConverter. This
     * constructor is forwarded directly to MutliplierActivity
     *
     * @param name name of activity
     * @param desc description of activity
     * @param function the multiplier argument. 
     * 
     * @return a new Activity
     */
    public Activity getProduction(String name, String desc, SourceFunction<Amount> function) {
        return new MultiplierActivity(name, desc, dataSource, function);

    }

    /**
     * <p>A build method for use of a volume argument that is
     * not wrapped into a DataSource. Its complementary taking an {@link AmountFunction}
     * as a parameter to provide support for complex multiplier argument. 
     * If non-null, the AmountFunction argument will be 
     * used for building the MultiplierActivity otherwise it will be ignored. </p> 
     * <p>
     * The volume argument (Amount) will first be wrapped into a dataSource
     * through the {@link wrapVolumeArgument(Amount amonut)} method and
     * subsequently processed through the current available {@link AmountConverter}.
     * </p>
     *
     *
     * @param name name of the created Activity
     * @param desc description of the created Activity
     * @param volume the amount to produce (will be processed through an
     * AmountConverter)
     * @param function an optional function 
     * @return
     */
    public Activity getProduction(String name, String desc, Amount volume, SourceFunction<Amount> function) {
        AmountDataSource volumeDS = wrapVolumeArgument(volume);
        AmountFunction multiplier = amountConverter.toCompatibleAmount(volumeDS);
        if (function != null) {
            multiplier = multiplier.multiply(function);
        }
        return new MultiplierActivity(name, desc, dataSource, multiplier);
    }

    /**
     * <p>A build method for use of raw (non-wrapped) volume and lifetime
     * arguments. If non-null, the SourceFunction argument will be used for
     * building the MultiplierActivity (by multiplication) otherwise it will be 
     * ignored.
     * </p> 
     * <p>
     * The volume and lifetime arguments will both be wrapped into 
     * dataSources through the {@link wrapVolumeArgument(Amount amount)} and
     * {@link wrapLifetimeArgument(Amount amount)} methods. The amount argument
     * will subsequently be processed through the current available 
     * {@link AmountConverter}.
     * </p>
     *
     *
     * @param name name of the created Activity
     * @param desc description of the created Activity
     * @param volume the amount to produce (will be processed through the
     * AmountConverter)
     * @param function an optional function
     * @return
     */
    public Activity getProduction(String name, String desc, Amount volume, Amount duration, SourceFunction<Amount> function) {
        //Wrap duration data as an AmountFunctionOLD
        AmountDataSource durationDS = wrapLifetimeArgument(duration);
        //create multiplier
        AmountFunction multiplier=new AmountFunction();
        //divide by duration
        multiplier.divide(durationDS);
        //multiply by aditional function if present
        if (function != null) {
            multiplier.multiply(function);
        }
        return getProduction(name, desc, volume, multiplier);
    }
}
